package robocup.component;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Date;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import robocup.component.actions.InitAction;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.Info;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.tactics.MyStateEval;
import robocup.connection.TeamSoccerScopeMediator;
import robocup.parser9.ParseException;
import robocup.parser9.RobocupClientParser;

/**
 * RobocupClient - main class for an agent, creates and initializes all
 * important objects like the worldmodel, parser, tactic and action.
 */
public class RobocupAgent
    extends AbstractAgent {

  // ~ Object Variables
  // ........................................................
  /**
   * These constants are used to read the config file. each agent -
   * configuartion should declare a property of the listed id with its own
   * value. If a value was detected, the defaut values are replaced
   */
  public static final String GUI_CONNECT = "gui-connect";

  public static final String STATES      = "statelist";

  /**
   * The agents tricot number, given by the server after initialization.
   */
  protected int              number;

  /**
   * Should the players world-model be connected to the gui? If this is the case
   * for at least one agent, the GUI is started automatically, and the agents
   * world-model is posted each cycle to the GUI (Monitor).
   */
  protected boolean          useGui;

  /**
   * A flag which enables to switch off the tactic, if desired.
   */
  protected final boolean          useTactic   = true;

  /**
   * The list of states, that should be instantiated for that agent. These must
   * be specified in the config-file, which is given to the agent as
   * property-object on startup.
   */
  protected String[]         states;

  /**
   * The synchro determines, when the robocup-agent should send the actions,
   * which were determined by the tactic.
   */
  private Synchro            synchro;

  /**
   * The prophet is a container for all situations, which are able to infer
   * knowledge out of the world-model, e.g. the next ball-interception point.
   */
  private Prophet            prophet;

  /**
   * The world-model captures the knowldege of the agent, which is directly
   * inferable from his perceptions.
   */
  private WorldModel         world;

  /**
   * The tactic is responsible for the determination of an action in a current
   * situation (represented by the world-model and the prophet within the
   * agent).
   */
  private StateLoader        stateLoader;

  // ~ Constructors
  /**
   * Creates a new instance of RobocupAgent, which stands for a player in the
   * robocup environment. Tthis class also holds the main-method, which is used
   * to start the agents/team.
   * 
   * @param props -
   *          contains all necessary data for initializing the agent
   */
  public RobocupAgent(
      final Properties props) {

    // read props
    super(props);
  }

  /**
   * This method overrrides the method from AbstractAgent, and initializes the
   * subclass dependent components. These are the synchro, the worldmodel, the
   * parser, the prophet, the action and the tactic.
   */
  @Override
  protected void initComponents() {

    this.synchro = new Synchro(this);
    this.prophet = new Prophet(this);
    this.world = new WorldModel(this);
    this.parser = new RobocupClientParser(new StringReader(""), this);
    this.prophet.init();
    this.stateLoader = new StateLoader(this, this.prophet);
  }

  /**
   * This method connects the Agent to the SoccerServer, and will return true,
   * if the Agent is connected afterwards. This is done by sending an
   * <code>InitAction</code> to the servers listening port (usually 6000).
   * After that, the server responses with initialization messages. These were
   * forwarded to the world-model, such that initialization can be finished
   * (e.g. the tricot number is contained in that message). Additionally, the
   * clang-version is communicated, and, if the <code>useGui</code> is set,
   * the agent is registered by the monitor.
   * 
   * @return true, if connection succeeds, else false
   */
  @Override
  protected boolean initProtocol() {

    try {
      // first connect
      final String init = new InitAction(this.mode, this.teamName).toString();
      this.connection.send(init);

      Info info = null;
      String rcv = null;
      while (true) {
        rcv = this.connection.receive();
        if (rcv == null) {
          System.out.println("Error: Server does not respond in time!");
          return false;
        }
        info = this.parser.parseCommand(rcv);
        if(info != null){
          final int type = info.getInfoType();
          this.informWorld(info);

          if (type == Info.TYPE_INIT) {
            this.stateLoader.loadStates();
            break; // leave the loop
          }
        }
      }

      // Register CLANG
      this.connection.send("(clang (ver 7 8))");
      System.err
          .println("[RobocupAgent] Agent " + this.number + " successfully connected!");
      if (this.useGui) {
        // init gui
        TeamSoccerScopeMediator
            .soccerServerConnectionDefaultHost(this.serverHost);
        TeamSoccerScopeMediator.registerPlayerBean(this);
      }
      
      // Register: Synch-See
      this.connection.send("(synch_see)");    
    }
    catch (final IOException e) {
      e.printStackTrace();
      return false;
    }
    catch (final ParseException p) {
      p.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * This method overrides the abstract method from AbstractAgent, which is
   * invoked always when an info is received from the soccerserver. Note that
   * this method isn't called, before initialization (<code>
   * initProtocol()</code>)
   * isn't complete.<br>
   * <br>
   * 
   * The synchro is the component, which decides how to react on infos. While
   * they all are forwarded to the world model, not all are used to invoke a
   * tactical decision process.
   * 
   * @param info -
   *          the received and parsed information (visual, acustic, sensebody or
   *          parameter)
   */
  @Override
  protected void processInfo(final Info info) {
	 
    this.informSynchro(info);
    
    // because of synch_see, we can never
    // receive a 2nd VI
    this.informWorld(info);
    
    if (info.getInfoType() == Info.TYPE_VISUAL) 
    	this.informTactic(info);
    
//    // world
//    if (!this.synchro.is2ndVI() 
//        || info.getInfoType() == Info.TYPE_AURAL_PSI 
//        || info.getInfoType() == Info.TYPE_AURAL) {
//      this.informWorld(info);
//    }
//    else {
//      // we already got the VI this cycle
//      // which means that this info has no new
//      // information
//      // --> only insert it into memory, don't
//      // update wm
//      this.world.getLongTermMemory().insertInfo(info);
//    }

    // tactic + gui
    // gui gets only informed if the info is used
    // for calculating an action
//    if (this.useTactic) {
//      // BS
//      if (info.getInfoType() == Info.TYPE_SENSE && this.synchro
//          .calculateBSAction()) {
//        // calculate action only in special
//        // cases (see synchro)
//        this.informTactic(info);
//      }
//      // VI
//      else if (info.getInfoType() == Info.TYPE_VISUAL) {
//        // first VI of the cycle?
//        if (!this.synchro.wasSent()) {
//          this.informTactic(info);
//
//        }
//        else {
//          // if 2 VIs arrived set ViewMode to
//          // 75ms to get
//          // a VI in next Cycle (shouldn't
//          // occur to often...)
//          // don't act as we already used the
//          // first VI
//          try {
//            this.connection.send(new ChangeViewModeAction(
//                ControllerNumbers.VIEW_QUALITY.HIGH,
//                ControllerNumbers.VIEW_ANGLE.NARROW).toString());
//            final AttentionToAction att = AttentionToActionFactory.getAction(
//                this.world.getSelfRef(),
//                this.world.getSelfRef().getLastState(), this.world,
//                this.prophet);
//            this.connection.send(att.toString());
//            this.connection.send(getProphet().getTurnNeckAction(this.world.getSelfRef().getLastState(),
//                this.synchro.getActions().getMain(), this.synchro.getActions().getView())
//                .toString());
//          }
//          catch (final IOException e) {
//            e.printStackTrace();
//          }
//        }
//      }
//      // AURAL
//      // CHANGE!!! do nothing as we act only after VIs (rw)
//      // left CSI as they might trigger behaviour !? (rw)
//      else if (// info.getType() == Info.TYPE_AURAL ||
//      // info.getType() == Info.TYPE_AURAL_PSI ||
//      info.getInfoType() == Info.TYPE_AURAL_CSI) {
//        // TODO consider VI arrivals of 300ms !? (rw)
//        this.informTactic(info);
//      }
//    }
  }

  /**
   * This method delegates the updates of the worldmodel with given info, e.g.
   * the coachVI will be sent to the coachworld, and the VI will be sent to the
   * worldmodel
   * 
   * @param info -
   *          a generic info
   */
  private void informWorld(final Info info) {

    this.world.update(info);
  }

  /**
   * This method initializes all necessary data, reading it from the properties
   * given for that agent on startup.
   */
  @Override
  protected void initConfig() {

    String tmp = null;
    tmp = (String) this.config.get(RobocupAgent.GUI_CONNECT);
    if (tmp != null) {
      this.useGui = tmp.startsWith("true");
    }

    tmp = (String) this.config.get(RobocupAgent.STATES);
    if (tmp != null) {
      final Vector<String> st = new Vector<String>();
      final StringTokenizer strTok = new StringTokenizer(tmp);
      while (strTok.hasMoreElements()) {
        st.add(strTok.nextToken());
      }

      this.states = new String[st.size()];
      for (int i = 0; i < st.size(); i++) {
        this.states[i] = st.elementAt(i);
      }
    }
  }

  /**
   * This method informs the synchro about the arrival of a BS or VI info
   * arrived at client.
   * 
   * @param info -
   *          the received info!
   */
  private void informSynchro(final Info info) {

    if (info.getInfoType() == Info.TYPE_SENSE) {
      this.synchro.arrivedBS((SenseBodyInfo) info);
    }
    if (info.getInfoType() == Info.TYPE_VISUAL) {
      this.synchro.arrivedVI((VisualInfo) info);
    }
    if (info.getInfoType() == Info.TYPE_AURAL) {
      this.synchro.arrivedAural((AuralInfo) info);
    }
  }

  /**
   * This method calls the tactic to compute a new action by stateevaluation and
   * calling the action component!
   * 
   * @param info -
   *          the recevied info!
   */
  private void informTactic(final Info info) {

    if (info == null || this.getMyStateEvaluation() == null) {
      return;
    }

    if (info.getInfoType() == Info.TYPE_SENSE) {
      //this.getMyStateEvaluation().update((SenseBodyInfo)info);
    }
    if (info.getInfoType() == Info.TYPE_VISUAL) {
      this.getMyStateEvaluation().update((VisualInfo)info);
    }
  }

  /**
   * This method sends a string to the server, usually the agents actions.
   * Additionally, the GUI is triggered after sending.
   * 
   * @param actions -
   *          the actions, which should by sent to the server using the defined
   *          syntax.
   */
  @Override
  public void sendActions(final String actions) {

    super.sendActions(actions);
    // if(!this.getWorld().isViBased())
    this.informGui();
  }

  /**
   * Informs soccerscope about the agents world model. This method is called
   * after sending so the visualized world is the one we based our action on.
   */
  private void informGui() {

    if (this.useGui && this.number > 0 && this.mode < 2) {
      TeamSoccerScopeMediator.registerInfoReceived(this);
      this.getMyStateEvaluation().addStatesToGUI();
    }
  }

  // ~ --------------------------------------------
  // ~ Methods for accessing the agents type and location
  /**
   * Sets the player number
   * 
   * @param num
   */
  public void setNumber(final int num) {

    this.number = num;
  }

  /**
   * Retrieves the player number
   * 
   * @return <code>this.number</code>
   */
  public int getNumber() {

    return this.number;
  }

  /**
   * Checks, if the player is goalie, by comparing the mode!
   * 
   * @return true, if the agent is a goalie.
   */
  public boolean isGoalie() {

    return (this.mode == AbstractAgent.GOALIE);
  }

  /**
   * Retreives the complete worldmodel
   * 
   * @return <code>this.world</code>
   */
  public WorldModel getWorld() {

    return this.world;
  }

  /**
   * Used by the tactic to inform the synchro that it can begin sending.
   */
  public void informFinishedActing() {

    this.synchro.send();
  }

  /**
   * Returns the state list, which was given on startup to the agent within the
   * property file of the agent.
   * 
   * @return an array containing all states as string values together with used
   *         playmodes!
   */
  public String[] getStates() {

    return this.states;
  }

  /**
   * Retrieves the quarter of the cycle, in which the next VI is expected to
   * arrive. This is determined by the synchro.
   * 
   * @return the quarter as integer [0,3]
   */
  public int getVIArrivalQuarter() {

    return this.synchro.getVIArrivalQuarter();
  }

  /**
   * Agents main class, which is used to start an agent or a complete team of
   * agents with the coach. The expected call is :<br>
   * <br>
   * 
   * java [-Dgui-connect=true|false] -jar &lt;dainamite.jar&gt; &lt;teamname&gt;
   * &lt;server-ip&gt; &lt;property-file-1&gt; [&lt;property-file-2&gt;] ...<br>
   * <br>
   * 
   * <ul>
   * <li> With <code>-Dgui-connect=true|false</code>, the usage of the
   * Monitor can be switched on or off globally. This value overrides all
   * settings regarding the useGui attribute of all given agent-property files.
   * This means, each agent started within the given JVM shares the useGui
   * attribute defined here. </li>
   * <li> The above java-call expects that the binaries are included into a
   * jar-archive together with the manifest given in etc/jar/manifest. There,
   * the main class (this one) is mentioned together with the dependent
   * libraries, which have the be located in the subdirectory specified there as
   * well (currently lib/ssj.jar is the only dependency). </li>
   * <li> The teamname is also global, i.e. it overrides that defined in all
   * agent property files. Hence it is not possible to start two team within the
   * same JVM yet. However, an appropriate extension should be easy. </li>
   * <li> The server-ip is global as well. </li>
   * <li>The last parameters should be the paths to the property files of the
   * agents. Hence one agent is created for each specified path, whereas the
   * order is important. At first, the goalie should be given, because it is
   * assumed that this one has the tricot number 1, and this is given to the
   * first agent, who connects to the server. After that, the player
   * configurations can be added. The coach should be always given last (it is
   * identified as coach, if the path contains "coach.conf"), because he tries
   * to set the heterogeneous player-types. This may fail, if not all agents are
   * connected to the server. Generally, any number of agents up to 11 plus
   * coach can be started, as long as the defined order is kept. Note that it is
   * sometimes necessary to give the absolute paths of the property-files in
   * order to ensure, the system will find them (e.g. when the team is started
   * remote). </li>
   * </ul>
   * 
   * @param args -
   *          must contain a team config-filename
   */
  public static void main(final String[] args) {

    if (args.length < 3) {
      System.out.println("[RobocupAgent][MAIN] First parameter: Teamname");
      System.out.println("[RobocupAgent][MAIN] Second parameter: Host");
      System.out
          .println("[RobocupAgent][MAIN] Third and more parameters: Player confs");
      System.out.println("[RobocupAgent][MAIN] Exit Program!");
      System.exit(1);
    }

    /**
     * Reading global properties
     */

    final String teamname = args[0];
    final String host = args[1];
    final String gui = System.getProperty(RobocupAgent.GUI_CONNECT);

    Date d = new Date();
    System.out.println("TIME: " + d.toString());
    /**
     * Creating a new agent for each property-id agentX, where X is an integer
     * smaller 12 and greater 0, by loading the correpsonding property file
     * given as property value from the team-property file! All global
     * properties overrides local props for easier change!
     */

    // loading agents
    for (int i = 2; i < args.length; i++) {
      final String file = args[i];

      // loading coach
      if (file.indexOf("coach.conf") >= 0) {

        try {
          Thread.sleep(3000);
        }
        catch (final Exception e) {
        	e.printStackTrace();
        }
        final Properties props = RobocupAgent.loadProperties(file);
        if (teamname != null) {
          props.put(AbstractAgent.TEAM_NAME, teamname);
        }
        if (host != null) {
          props.put(AbstractAgent.HOST_NAME, host);
        }

        if (props != null) {
          System.err.println("Successfully loaded properties from " + file);
          final CoachAgent agent = new CoachAgent(props);
          agent.initialize();
          agent.start();
        }
        continue;
      }

      if (file == null) {
        continue;
      }

      final Properties props = RobocupAgent.loadProperties(file);

      if (props == null) {
        System.err.println("Failed to load properties from " + file);
        continue;
      }

      // update gloabl props:
      if (teamname != null) {
        props.put(AbstractAgent.TEAM_NAME, teamname);
      }
      if (host != null) {
        props.put(AbstractAgent.HOST_NAME, host);
      }
      if (gui != null) {
        props.put(RobocupAgent.GUI_CONNECT, gui);
      }

      System.err.println("Successfully loaded properties from " + file);
      final RobocupAgent agent = new RobocupAgent(props);
      agent.initialize();
      agent.start();
    }
  }

  /**
   * This method loads the properties of an agent from a file with a given
   * filename. It is used to create a <code>Property</code> object out of the
   * given paths during startup of the agents (@see main()).
   * 
   * @param fileName
   *          file with properties
   * @return Properties - loaded properties
   */
  public static Properties loadProperties(final String fileName) {

    final Properties p = new Properties();
    try {
      p.load(new FileInputStream(fileName));
    }
    catch (final Exception e) {
      URL url = RobocupAgent.class.getClassLoader().getResource(fileName);
      if (url == null) {
        url = RobocupAgent.class.getClassLoader().getResource(
            "etc/agents/" + fileName);
      }
      if (url == null) {
        e.printStackTrace();
        return null;
      }
      try {
        p.load(new FileInputStream(url.getFile()));
      }
      catch (final Exception e1) {
        e1.printStackTrace();
        return null;
      }
    }

    return p;
  }

  /**
   * This method returns the last sent command to the SoccerServer
   * 
   * @return - a string with the last command
   */
  public String getCommand() {

    return this.connection.getCommand();
  }

  /**
   * @return Returns the prophet.
   */
  public Prophet getProphet() {

    return this.prophet;
  }

  /**
   * Checks, whether the gui is used by the agent or not.
   * 
   * @return <code>this.useGui</code>
   */
  public boolean useGui() {

    return this.useGui;
  }

  /**
   * @return Returns the synchro.
   */
  public Synchro getSynchro() {

    return this.synchro;
  }
  
  /**
   * @return this players state evaluation
   */
  private MyStateEval getMyStateEvaluation(){
    return (MyStateEval)this.world.getSelfRef().getMyStateEval();
  }
}
